﻿using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using ClassLibrary1;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests
{
    [TestClass]
    public class HalsteadCacheMemberTest : AnalyzerTestBase
    {
        private MemberDescriptor _getter;
        private MemberDescriptor _switchMember;
        private HalsteadCache _halsteadCache;
        private IDependencyModel _model;

        [TestInitialize]
        public void Initialize()
        {
            _model = new DependencyModel();
            _getter = PrepareGetter(_model);
            _switchMember = PrepareSwitch(_model);
            _halsteadCache = new HalsteadCache();
        }

        public static MemberDescriptor PrepareSwitch(IDependencyModel model)
        {
            var switchMember = DescriptorBuilder.BuildMemberDescriptor(typeof(CyclomaticClass).GetMethod("Switch"));
            switchMember.Visibility = NodeVisibility.Ignore;
            var instructions = new List<Instruction>
            {
                new Instruction {OpCode = OperationCode.Ldarg_1},
                new Instruction {OpCode = OperationCode.Stloc_0},
                new Instruction {OpCode = OperationCode.Ldloc_0},
                new Instruction {OpCode = OperationCode.Ldc_I4_1},
                new Instruction {OpCode = OperationCode.Sub},
                new Instruction {OpCode = OperationCode.Switch},
                new Instruction {OpCode = OperationCode.Br_S},
                new Instruction {OpCode = OperationCode.Ldstr},
                new Instruction {OpCode = OperationCode.Call},
                new Instruction {OpCode = OperationCode.Ret},
                new Instruction {OpCode = OperationCode.Ldstr},
                new Instruction {OpCode = OperationCode.Call},
                new Instruction {OpCode = OperationCode.Ret},
                new Instruction {OpCode = OperationCode.Ldstr},
                new Instruction {OpCode = OperationCode.Call},
                new Instruction {OpCode = OperationCode.Ret},
            };
            model.Add(switchMember);
            model.InstructionCache.SetInstructions(switchMember, instructions);
            model.DependenciesPerMemberOut[switchMember] = Model.DependenciesPerMemberOut[switchMember];
            return switchMember;
        }

        public static MemberDescriptor PrepareGetter(IDependencyModel model)
        {
            var getter = DescriptorBuilder.BuildMemberDescriptor(typeof(PureDataObject).GetMethod("get_One"));
            getter.Visibility = NodeVisibility.Ignore;
            var instructions = new List<Instruction>
            {
                new Instruction {OpCode = OperationCode.Ldarg_0},
                new Instruction {OpCode = OperationCode.Ldfld},
                new Instruction {OpCode = OperationCode.Ret}
            };
            model.InstructionCache.SetInstructions(getter, instructions);

            var typeString = DescriptorBuilder.BuildTypeDescriptor(typeof(string));
            var compilerGenerated = DescriptorBuilder.BuildTypeDescriptor(typeof(CompilerGeneratedAttribute));
            var backingField =
                Model.DependenciesPerMemberOut.Keys.First(
                    x => x.FullName == "ClassLibrary1.PureDataObject.§System.String <One>k__BackingField");
            var uninstrumentedDependencies = new[]
            {
                typeString, compilerGenerated, backingField
            };
            var filteredDependencies =
                new HashSet<DependencyDescriptor>(
                    Model.DependenciesPerMemberOut[getter].Where(x => uninstrumentedDependencies.Contains(x.Target)));
            model.DependenciesPerMemberOut[getter] = filteredDependencies;
            getter.VariableCount = 0;
            return getter;
        }

        [TestMethod]
        public void MemberDistinctOperatorsWhenGetterThenOne()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_getter, _model);
            Assert.AreEqual(1, cacheEntry.DistinctOperators);
        }

        [TestMethod]
        public void MemberTotalOperatorsWhenGetterThenOne()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_getter, _model);
            Assert.AreEqual(1, cacheEntry.TotalOperators);
        }

        [TestMethod]
        public void MemberTotalOperandsWhenGetterThenOne()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_getter, _model);
            Assert.AreEqual(1, cacheEntry.TotalOperands);
        }

        [TestMethod]
        public void MemberDistinctOperandsWhenSetterThenOne()
        {
            var member = DescriptorBuilder.BuildMemberDescriptor(typeof(PureDataObject).GetMethod("set_One"));
            var cacheEntry = _halsteadCache.GetCacheEntry(member, Model);
            Assert.AreEqual(1, cacheEntry.DistinctOperators);
        }

        [TestMethod]
        public void MemberDistinctOperandsWhenExceptionThenOne()
        {
            var member = DescriptorBuilder.BuildMemberDescriptor(typeof(ClassObject).GetMethod("Hello"));
            var cacheEntry = _halsteadCache.GetCacheEntry(member, Model);
            Assert.AreEqual(1, cacheEntry.DistinctOperands);
        }

        [TestMethod]
        public void MemberDistinctOperandsWhenGetterThenOne()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_getter, _model);
            Assert.AreEqual(1, cacheEntry.DistinctOperands);
        }

        [TestMethod]
        public void MemberDistinctOperandsCountAccessedMemberAndLocalAndType()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_switchMember, _model);
            Assert.AreEqual(2, cacheEntry.DistinctOperands);
        }

        [TestMethod]
        public void MemberDistinctOperatorsCountCyclomaticInstruction()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_switchMember, _model);
            Assert.AreEqual(3, cacheEntry.DistinctOperators);
        }

        [TestMethod]
        public void MemberTotalOperatorsCountBranches()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_switchMember, _model);
            Assert.AreEqual(5, cacheEntry.TotalOperators);
        }

        [TestMethod]
        public void MemberTotalOperandsCountEachMemberCall()
        {
            var cacheEntry = _halsteadCache.GetCacheEntry(_switchMember, _model);
            Assert.AreEqual(5, cacheEntry.TotalOperands);
        }

        [TestMethod]
        public void CountDistinctOperatorInstructions()
        {
            DependencyModel model;
            var member = PrepareMemberWithOperatorsOnly(out model);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(6, cacheEntry.DistinctOperators);
        }

        private static MemberDescriptor PrepareMemberWithOperatorsOnly(out DependencyModel model)
        {
            var member = new MemberDescriptor
            {
                Identifier = "++CountDistinctOperatorInstructions++",
                FullName = "++CountDistinctOperatorInstructions++",
                ImplementingType = new TypeDescriptor(),
                MemberType = MemberType.Method
            };
            var instructions = new List<Instruction>
            {
                new Instruction {OpCode = OperationCode.Add},
                new Instruction {OpCode = OperationCode.Div},
                new Instruction {OpCode = OperationCode.Mul_Ovf_Un},
                new Instruction {OpCode = OperationCode.Div},
                new Instruction {OpCode = OperationCode.And},
                new Instruction {OpCode = OperationCode.And},
                new Instruction {OpCode = OperationCode.Br_S},
                new Instruction {OpCode = OperationCode.Add},
                new Instruction {OpCode = OperationCode.Add},
                new Instruction {OpCode = OperationCode.Div},
                new Instruction {OpCode = OperationCode.Or},
                new Instruction {OpCode = OperationCode.Div},
                new Instruction {OpCode = OperationCode.Or},
                new Instruction {OpCode = OperationCode.Or},
                new Instruction {OpCode = OperationCode.Mul_Ovf_Un},
                new Instruction {OpCode = OperationCode.Mul_Ovf_Un},
            };
            model = new DependencyModel();
            model.Add(member);
            model.InstructionCache.SetInstructions(member, instructions);
            return member;
        }

        [TestMethod]
        public void CountTotalOperatorInstructions()
        {
            DependencyModel model;
            var member = PrepareMemberWithOperatorsOnly(out model);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(16, cacheEntry.TotalOperators);
        }

        [TestMethod]
        public void FieldMethodAndLocalOpCodeCountToOperands()
        {
            DependencyModel model;
            var member = PrepareMemberWithOperandsOnly(out model);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(16, cacheEntry.TotalOperands);
        }

        [TestMethod]
        public void FieldAndMethodOpCodeCountToOperators()
        {
            DependencyModel model;
            var member = PrepareMemberWithOperandsOnly(out model);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(10, cacheEntry.TotalOperators);
        }

        private static MemberDescriptor PrepareMemberWithOperandsOnly(out DependencyModel model)
        {
            var member = new MemberDescriptor
            {
                Identifier = "++CountDistinctOperandsInstructions++",
                FullName = "++CountDistinctOperandsInstructions++",
                ImplementingType = new TypeDescriptor(),
                MemberType = MemberType.Method
            };
            var instructions = new List<Instruction>
            {
                new Instruction {OpCode = OperationCode.Call},
                new Instruction {OpCode = OperationCode.Calli},
                new Instruction {OpCode = OperationCode.Callvirt},
                new Instruction {OpCode = OperationCode.Newobj},
                new Instruction {OpCode = OperationCode.Ldfld},
                new Instruction {OpCode = OperationCode.Ldvirtftn},
                new Instruction {OpCode = OperationCode.Ldfld},
                new Instruction {OpCode = OperationCode.Tail_},
                new Instruction {OpCode = OperationCode.Ldloc},
                new Instruction {OpCode = OperationCode.Ldftn},
                new Instruction {OpCode = OperationCode.Stloc_2},
                new Instruction {OpCode = OperationCode.Ldftn},
                new Instruction {OpCode = OperationCode.Ldloc_1},
                new Instruction {OpCode = OperationCode.Ldftn},
                new Instruction {OpCode = OperationCode.Ldloc},
                new Instruction {OpCode = OperationCode.Ldftn},
            };
            model = new DependencyModel();
            model.Add(member);
            model.InstructionCache.SetInstructions(member, instructions);
            return member;
        }

        [TestMethod]
        public void VariablesCountToDistinctOperands()
        {
            var member = new MemberDescriptor
            {
                Identifier = "++CountDistinctOperandsInstructions++",
                FullName = "++CountDistinctOperandsInstructions++",
                ImplementingType = new TypeDescriptor(),
                MemberType = MemberType.Method,
                VariableCount = 5
            };
            var instructions = new List<Instruction>();
            var model = new DependencyModel();
            model.Add(member);
            model.InstructionCache.SetInstructions(member, instructions);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(5, cacheEntry.DistinctOperands);
        }

        [TestMethod]
        public void AccessorsCountToOperators()
        {
            var member = new MemberDescriptor
            {
                Identifier = "++CountDistinctOperandsInstructions++",
                FullName = "++CountDistinctOperandsInstructions++",
                ImplementingType = new TypeDescriptor(),
                MemberType = MemberType.Method,
                Visibility = NodeVisibility.Private,
                IsAbstract = true,
                IsStatic = true
            };
            var instructions = new List<Instruction>();
            var model = new DependencyModel();
            model.Add(member);
            model.InstructionCache.SetInstructions(member, instructions);
            var cacheEntry = _halsteadCache.GetCacheEntry(member, model);
            Assert.AreEqual(3, cacheEntry.TotalOperators);
        }

        [Ignore]
        [TestMethod]
        public void WhenTypeThenCreateMemberEntriesToo()
        {
        }

        [Ignore]
        [TestMethod]
        public void TypeDistinctOperatorsWhenDataObjectThenCallIsOnlyOperator()
        {
            var dataObject = DescriptorBuilder.BuildTypeDescriptor(typeof(PureDataObject));
            var cacheEntry = _halsteadCache.GetCacheEntry(dataObject, Model);
            Assert.AreEqual(1, cacheEntry.DistinctOperators);
        }

        [Ignore]
        [TestMethod]
        public void TypeTotalOperatorsWhenDataObjectThenSumUpCalls()
        {
            var dataObject = DescriptorBuilder.BuildTypeDescriptor(typeof(PureDataObject));
            var cacheEntry = _halsteadCache.GetCacheEntry(dataObject, Model);
            Assert.AreEqual(8, cacheEntry.TotalOperators);
        }
    }
}
